/**
*
*	clipboard.c - Modulo generico gestionado por el clipboard manager
*
*/

#include "clipboard_work.h"

/* Licencia del modulo */
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Alvaro Perez Liaño - Xavier Gallofre Nieva");

/* Estructura de los ficheros en /proc */
static struct proc_dir_entry *proc_clipboard;
static struct proc_dir_entry *proc_period;

/* Estructura de los directorios en /proc */
static struct proc_dir_entry *proc_dir;
/* IMPORTANTE - Esta variable viene dada por el clipboard manager */
extern struct proc_dir_entry *proc_dir_aiso;

/* IMPORTANTE - Esta variable veiene dada por el clipboard manager */
extern struct workqueue_struct *workclip;

/* Estructura de cada trabajo encolado en la workqueue */
typedef struct {
  struct work_struct my_work;
  char* buffer;
} my_work_t;

/* Estructura para trabajos encolados con tiempo de espera (delayed) */
typedef struct {
  struct delayed_work my_dwork;
  char* buffer;
} my_dwork_t;

/* Entero para indicar si esta activa la entra period (valor != 0)*/
short delayed_work;

/* Buffers para la entradas clipboard y periodo */
static char *clipboard_buffer;
static char *period_buffer;

/* Buffer auxiliar para poder dar formato a la entrada del usuario*/
static char *my_buffer;

/**************************** INICIO FUNCIONES DE TRABAJO DE LA WORQUEUE *******************************/ 
void show_log(struct work_struct *work)
{
	my_work_t *my_work = (my_work_t *)work;

	printk(KERN_ALERT "Contenido del buffer del clipboard \"%s\": %s\n", __this_module.name, my_work->buffer);
	printk(KERN_ALERT "Numero de caracteres del buffer: %i\n", strlen(clipboard_buffer));
	vfree(work);

	return;
}

void show_log_delayed(struct work_struct *dwork)
{
	my_dwork_t *my_dwork = (my_dwork_t *)dwork;

	printk(KERN_ALERT "Contenido del buffer del clipboard %s: %s\n", __this_module.name, my_dwork->buffer);
	vfree(dwork);
	printk(KERN_ALERT "Parada la notificacion periodica del modulo: %s\n", __this_module.name);
	if (delayed_work == 1) {
		printk(KERN_ALERT "Se notificará en 2000 jiffies otra vez el modulo: %s\n", __this_module.name);
		my_dwork = (my_dwork_t *)vmalloc(sizeof(my_dwork_t));
		if (my_dwork) {
			INIT_DELAYED_WORK((struct delayed_work *)my_dwork, show_log_delayed);
			my_dwork->buffer = clipboard_buffer;
			// El valor viene dado en jiffies, no en milisegundos
			queue_delayed_work(workclip, (struct delayed_work *)my_dwork, 2000);
		}
	}
	return;
}
/**************************** FIN FUNCIONES DE TRABAJO DE LA WORQUEUE  *********************************/ 

/**************************** INICIO FUNCIONES LECTURA/ESCRITURA *******************************/ 
/* Funcion de lectura del clipboard activo */
int read_proc_clipboard(char *buffer, char **buffer_location, off_t offset, int buffer_length, int *eof, void *data)
{
	if (offset > 0 || clipboard_buffer == NULL) {
		return 0;
	} else {
		memcpy(buffer, clipboard_buffer, strlen(clipboard_buffer));			
	}
	return strlen(clipboard_buffer);
}

/* Funcion de lectura del clipboard "periodo" */
int read_proc_period(char *buffer, char **buffer_location, off_t offset, int buffer_length, int *eof, void *data)
{
	if (offset > 0 || period_buffer == NULL) {
		return 0;
	} else {
		memcpy(buffer, period_buffer, strlen(period_buffer));	
	}
	return strlen(period_buffer);
}

/* Funcion de escritura del clipboard activo */
int write_proc_clipboard(struct file *filepointer, const char __user *buffer, unsigned long len, void *data)
{
	my_work_t *work;
	int length;
	if (len > MAX_LENGTH) {
		length = MAX_LENGTH;
	} else {
		length = len;
	}
	// Escritura en el clip
	if (copy_from_user(my_buffer, buffer, length)) {
		printk(KERN_ALERT "Imposible escribir\n");
		return -EFAULT;
	}
	
	sscanf(my_buffer, "%s", clipboard_buffer);	
	if (delayed_work == 0) {		
		work = (my_work_t *)vmalloc(sizeof(my_work_t));
		if (work) {
		  INIT_WORK((struct work_struct *)work, show_log);
		  work->buffer = clipboard_buffer;
		  queue_work(workclip, (struct work_struct *)work);
		}
    }

	return length;
}

/* Funcion de escritura del clipboard "periodo" */
int write_proc_period(struct file *filepointer, const char __user *buffer, unsigned long len, void *data)
{
	my_dwork_t *dwork;
	int length;
	long i = simple_strtol(buffer, NULL, 10);
	if (len > MAX_LENGTH) {
		length = MAX_LENGTH;
	} else {
		length = len;
	}
	// Escritura en el clip
	if (copy_from_user(my_buffer, buffer, length)) {
		printk(KERN_ALERT "Imposible escribir\n");
		return -EFAULT;
	}
	
	sscanf(my_buffer, "%s", period_buffer);
	if (i != 0 && delayed_work == 0) {
		delayed_work = 1;		
		dwork = (my_dwork_t *)vmalloc(sizeof(my_dwork_t));
		if (dwork) {
			INIT_DELAYED_WORK((struct delayed_work *)dwork, show_log_delayed);
			dwork->buffer = clipboard_buffer;
			queue_delayed_work(workclip, (struct delayed_work *)dwork, 100);
		}
	} else if (i == 0 && delayed_work == 1) {
			delayed_work = 0;
			flush_workqueue(workclip);
	}
	
	return length;
}
/**************************** FIN FUNCIONES LECTURA/ESCRITURA *********************************/ 

/* Funcion invocada al cargar el modulo */
int modulo_carga(void) 
{
	clipboard_buffer = (char *)vmalloc(MAX_LENGTH);	
	period_buffer = (char *)vmalloc(MAX_LENGTH);
	my_buffer = (char *)vmalloc(MAX_LENGTH);
	// Inicialmente period = 0
	delayed_work = 0;
	
	printk(KERN_ALERT "Cargado el modulo %s\n",__this_module.name);
	
	// Creamos el directorio con el nombre del clipboard
	if (create_proc_dir_custom(__this_module.name, &proc_dir, &proc_dir_aiso) == -ENOMEM) return -ENOMEM;
	
	// Creamos la entrada CLIPBOARD
	if (create_proc_entry_custom(CLIPBOARD, &proc_clipboard, &proc_dir) == -ENOMEM) return -ENOMEM;
	// Asociamos funciones de lectura y escritura y damos permisos
	proc_clipboard->read_proc		= read_proc_clipboard;
	proc_clipboard->write_proc 		= write_proc_clipboard;
	proc_clipboard->mode 			= S_IFREG | S_IRUGO;

	// Creamos la entrada PERIOD
	if (create_proc_entry_custom(PERIOD, &proc_period, &proc_dir) == -ENOMEM) return -ENOMEM;
	// Asociamos funciones de lectura y escritura y damos permisos
	proc_period->read_proc			= read_proc_period;
	proc_period->write_proc 		= write_proc_period;
	proc_period->mode 				= S_IFREG | S_IRUGO;

	return 0;
} 

/* Funcion invocada al descargar el modulo */
void modulo_descarga(void) 
{
	printk(KERN_ALERT "Descargado el modulo %s\n", __this_module.name);
	
	// Eliminamos la entrada CLIPBOARD
	remove_proc_entry_custom(CLIPBOARD, &proc_clipboard, &proc_dir);
	
	// Eliminamos la entrada PERIOD
	remove_proc_entry_custom(PERIOD, &proc_period, &proc_dir);
	
	// Eliminamos el directorio con el nombre del clip
	remove_proc_dir_custom(__this_module.name, &proc_dir, &proc_dir_aiso);
	
	// Dejamos de encolar trabajos
	delayed_work = 0;
	
	// Limpiamos los buffers
	vfree(period_buffer);
	vfree(clipboard_buffer);
	vfree(my_buffer);
}

/* Funciones init y exit */
module_init(modulo_carga);
module_exit(modulo_descarga);